package util;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.Set;
import plume.Pair;
import model.Filter;
public class CanBeUsedAs {
	  public static Map<Pair<Class<?>, Class<?>>, Boolean> canBeUsedCache =
		     new LinkedHashMap<Pair<Class<?>, Class<?>>, Boolean>();

	  public static boolean canBeUsedAs(Class<?> c1, Class<?> c2) { 
		  if (c1 == null || c2 == null)
			  throw new IllegalArgumentException("Parameters cannot be null.");
		  
			 if(c2.equals(Comparable.class)&& ! (c1 ==  java.lang.Integer.class||c1 == int.class)) 
				 return false;

					 
		     if (c1.isArray() && c2.equals(Object.class))
		           return true;
		     if(Filter.isPrimitiveOrStringType(c1)&&c2.equals(Object.class))
		    	  return true;
		     
		     if (c1.equals(c2)&&c1.equals(java.util.List.class))
			       return true;
		     
		  if (!(c1.isArray()&&c2.isArray()))
			 if(c1.equals(Object.class)
				||c1.equals(java.util.HashMap.class)
				|| c1.equals(Comparable.class)
				|| Filter.isAbstract(c1) ){
				 Pair<Class<?>, Class<?>> classPair = new Pair<Class<?>, Class<?>>(c1, c2);
				 canBeUsedCache.put(classPair, false);
			  return false;
			 }

		  

	     if (c1.equals(void.class) && c2.equals(void.class))
	       return true;
		 if(Filter.isPrimitiveOrStringType(c1)&&c2.equals(java.lang.Class.class))
			 return true;

	     
	     if (c1.equals(void.class) || c2.equals(void.class))
	       return false;
	     Pair<Class<?>, Class<?>> classPair = new Pair<Class<?>, Class<?>>(c1, c2);
	     Boolean cachedRetVal = canBeUsedCache.get(classPair);
	     boolean retval;
	     if (cachedRetVal == null) {
	       retval = canBeUsedAs0(c1, c2);
	       canBeUsedCache.put(classPair, retval);
	     } else {
	       retval = cachedRetVal;
	     }
	     return retval;
	  }

		   private static boolean canBeUsedAs0(Class<?> c1, Class<?> c2) {
			     if (c1.isArray()) {
			         if (c2.equals(Object.class))
			           return true;
			         if (!c2.isArray())
			           return false;
			         Class<?> c1SequenceType = c1.getComponentType();
			         Class<?> c2componentType = c2.getComponentType();

			         if (c1SequenceType.isPrimitive()) {
			           if (c2componentType.isPrimitive()) {
			             return (c1SequenceType.equals(c2componentType));
			           } else {
			             return false;
			           }
			         } else {
			           if (c2componentType.isPrimitive()) {
			             return false;
			           } else {
			             c1 = c1SequenceType;
			             c2 = c2componentType;
			           }
			         }
			       }

			     if (c1.isPrimitive())
				       c1 = CanBeUsedAs.boxedType(c1);
				     if (c2.isPrimitive())
				       c2 = CanBeUsedAs.boxedType(c2);

			       boolean ret = false;

			       if (c1.equals(c2)) { // XXX redundant (see canBeUsedAs(..)).
			         ret = true;
			       } else if (c2.isInterface()) {
			         Set<Class<?>> c1Interfaces = getInterfacesTransitive(c1);
			         if (c1Interfaces.contains(c2))
			           ret = true;
			         else
			           ret = false;
			       } else if (c1.isInterface()) {
			         // c1 represents an interface and c2 a class.
			         // The only safe possibility is when c2 is Object.
			         if (c2.equals(Object.class))
			           ret = true;
			         else
			           ret = false;
			       }else if (c2.equals(java.lang.Class.class)) {
			    	   ret=true;
			    	   
			       }else {
			         ret = isSubclass(c1, c2);
			       }
			       return ret;
			     }
		   private static boolean isSubclass(Class<?> c1, Class<?> c2) {
			     assert(c1 != null);
			     assert(c2 != null);
			     assert(!c1.equals(Void.TYPE));
			     assert(!c2.equals(Void.TYPE));
			     assert(!c1.isInterface());
			     assert(!c2.isInterface());
			     return c2.isAssignableFrom(c1);
			   }
		   private static Set<Class<?>> getInterfacesTransitive(Class<?> c1) {

			     Set<Class<?>> ret = new LinkedHashSet<Class<?>>();

			     Class<?>[] c1Interfaces = c1.getInterfaces();
			     for (int i = 0; i < c1Interfaces.length; i++) {
			       ret.add(c1Interfaces[i]);
			       ret.addAll(getInterfacesTransitive(c1Interfaces[i]));
			     }

			     Class<?> superClass = c1.getSuperclass();
			     if (superClass != null)
			       ret.addAll(getInterfacesTransitive(superClass));

			     return ret;
			   }
		   private static final Map<Class<?>, Class<?>> primitiveAndStringToBoxed
		   = new LinkedHashMap<Class<?>, Class<?>>(8);

		   static {
		     primitiveAndStringToBoxed.put(boolean.class, Boolean.class);
		     primitiveAndStringToBoxed.put(byte.class, Byte.class);
		     primitiveAndStringToBoxed.put(char.class, Character.class);
		     primitiveAndStringToBoxed.put(double.class, Double.class);
		     primitiveAndStringToBoxed.put(float.class, Float.class);
		     primitiveAndStringToBoxed.put(int.class, Integer.class);
		     primitiveAndStringToBoxed.put(long.class, Long.class);
		     primitiveAndStringToBoxed.put(short.class, Short.class);
		     primitiveAndStringToBoxed.put(String.class, String.class); 
		     

		     
		     
		   }
		   public  static Class<?> boxedType(Class<?> c1) {
			    return primitiveAndStringToBoxed.get(c1);
			  }


}
